/*

Write MaxMSP objects in C++

 */
 
#ifndef MAXMSP_CPP_H
#define MAXMSP_CPP_H

#include <vector>
 
#include "ext.h"
#include "ext_common.h"
#include "ext_obex.h"
#include "ext_path.h"
#include "z_dsp.h"
#include "buffer.h"	


/* for osx stuff */
//#include <MaxAPI/MaxAPI.h>
//#include <MaxAudioAPI/MaxAudioAPI.h>

#define MAX_CPP_VERSION	0.001

#ifndef DEBUG
#define DEBUG(...)	
#endif

// for A_NOTHING methods (e.g. bang):
#define REGISTER_METHOD(CLASS, METHOD)	class_addmethod(	\
	(t_class *)CLASS::maxClass,								\
	(method)CLASS::MaxMethod<&CLASS::METHOD>::call,			\
	#METHOD,												\
	0);	
	
// for A_GIMME methods (t_symbol * s, long argc, void * argv):
#define REGISTER_METHOD_GIMME(CLASS, METHOD)	class_addmethod(	\
	(t_class *)CLASS::maxClass,								\
	(method)CLASS::MaxMethodGimme<&CLASS::METHOD>::call,	\
	#METHOD,												\
	A_GIMME,												\
	0);	
	
// for ASSIST methods (...):
#define REGISTER_METHOD_ASSIST(CLASS, METHOD)	class_addmethod(	\
	(t_class *)CLASS::maxClass,								\
	(method)CLASS::MaxMethodAssist<&CLASS::METHOD>::call,	\
	#METHOD,												\
	A_GIMME,												\
	0);	

// this avoids the problem of calcoffset() throwing warnings 
#define calcoffset_cpp(OBJECT, MEMBER) \
          ((size_t) ((char *)&(OBJECT->MEMBER) - (char*)OBJECT))

// abstraction to provide inlet & outlet signals in a safer way
typedef std::vector<t_sample *> signalArray;

template <class T>
class MaxCpp
{
public:
	// for Max external-loading & Obex:
	t_pxobject			maxOb;		// Max 'instance' pointer
	void *				maxObex;	// Max Obex container
	static void *		maxClass;	// Max 'class' pointer
	static char *		maxClassName; // our own reference
	
protected:
	unsigned int		maxEnable;	// general purpose

	signalArray			maxMSPInlets;
	signalArray			maxMSPOutlets;
	unsigned int		maxMSPNumInlets;
	unsigned int		maxMSPNumOutlets;
	unsigned int		maxMSPEnable;
	static void			maxMSPState(T * x, long n);
	static void			maxMSPSetup(T * x, t_signal **sp, short *count);
	static t_int *		maxMSPPerform(t_int *w);
	static void (T::*maxMSPUserCallback)(unsigned long, signalArray &, signalArray &);

	static void	*		maxAlloc(t_symbol *s, long argc, t_atom *argv);
	static void			maxFree(T *x);
	static void *		operator new(size_t);
	static void			operator delete(void *);
	
	// use normal C++ constructors & destructors:
	MaxCpp();
	~MaxCpp();	
	
	typedef void (T::*maxmethodgimme)(t_symbol *, long, t_atom *);
	typedef void (T::*maxmethodassist)(void*, long, long, char*);
	typedef void (T::*maxmethod)();
	template<maxmethodgimme F>
	struct MaxMethodGimme
	{
		static void call(T * x, t_symbol * s, long ac, t_atom * av) { ((x)->*F)(s, ac, av); }
	};
	
	template<maxmethodassist F>
	struct MaxMethodAssist
	{
		static void call(T * x, void * box, long msg, long arg, char * dstString) { ((x)->*F)(box, msg, arg, dstString); }
	};
		
	template<maxmethod F>
	struct MaxMethod
	{
		static void call(T * x) { ((x)->*F)(); }
	};
	
public:	

	// typedefs
	typedef void (T::*maxMSPCallbackType)(unsigned long, signalArray &, signalArray &);

	// called from 'main' to create class ptr
	static t_class *	maxMakeClass(char * name);	// use this for Max objects (FOR INITIALIZING MAX OBJECTS)
	static t_class *	maxMSPMakeClass(char * name, maxMSPCallbackType);	// use this for MSP objects
	
	// 'dump' obex outlet:
	t_object *			maxObexOutlet;
	
	// audio stuff:
	void maxMSPCreate(unsigned int, unsigned int);
	void maxMSPEnd();
};

// singleton class object
template <class T>
void * MaxCpp<T>::maxClass = 0;
template <class T>
char * MaxCpp<T>::maxClassName;
template <class T>
void (T::*MaxCpp<T>::maxMSPUserCallback)(unsigned long, signalArray &, signalArray &);

template <class T>
inline t_class * MaxCpp<T>::maxMakeClass(char * name)
{
	/* 3. define your object class */
	t_class * c = class_new(
		name,							// name of the class
		(method)MaxCpp<T>::maxAlloc,	// method new (allocate this object)
		(method)MaxCpp<T>::maxFree,		// method free (deallocate this object)
		(short)sizeof(T),				// what is the size of this object in bytes
		0L,								// method mmenu
		A_GIMME,						// takes an argument list
	0);
	
	DEBUG("new class %p %s", c, name);
	
	maxClassName = name;
		
	// a dummy class pointer in order to use in offsetof_in_object
	T * dummy;	
	
	/* 4. set the obex offset */
	// register the byte offset of obex with the class
	class_obexoffset_set(c, calcoffset_cpp(dummy, maxObex));
	
	/* 4a. initialize common symbols */
	// initialize the common symbols, since we want to use them
	common_symbols_init(); // this is usually initialized in the main() function	

	// @enable
	class_addattr(c, (t_object *)attr_offset_new(
		"enable",				// name
		_sym_long,				// type
		0,						// attrflags, 
		(method)0L,				// getter
		(method)0L,				// setter
		calcoffset_cpp(dummy, maxEnable)	// position within class
	));

	// add methods for dumpout and quickref	
	class_addmethod(c, (method)object_obex_dumpout,	"dumpout",		A_CANT, 0); 
	class_addmethod(c, (method)object_obex_quickref,"quickref",		A_CANT, 0);
	
	// store static ref
	T::maxClass = c;
	
	
	
	return c;
}

template <class T>
inline void * MaxCpp<T> :: maxAlloc(t_symbol * sym, long ac, t_atom * av)
{
	T * t = new T(sym, ac, av);
	DEBUG("alloc %s %p", T::maxClassName, t);
	return (void *)t;
}

template <class T>
inline void MaxCpp<T> :: maxFree(T * x)
{
	DEBUG("maxFree");
	delete x;
}

template <class T>
void * MaxCpp<T> :: operator new(size_t t)
{
	void * v = object_alloc((t_class *)T::maxClass);
	DEBUG("operator new %s %p (class %p)", T::maxClassName, v, T::maxClass);
	return v;
}

template <class T>
void MaxCpp<T> :: operator delete(void *)
{
	DEBUG("operator delete");
	// nothing to do - Max will manage the memory
}

template <class T>
MaxCpp<T>::MaxCpp()
{		
	DEBUG("constructor");
	
	// add a generic outlet
	maxObexOutlet = (t_object *)outlet_new(this, 0L);
	object_obex_store(this, _sym_dumpout, maxObexOutlet); // also set it as dumpout
	
	// by default:
	maxEnable = 1;	
	
	DEBUG("new %s %p ob %p obex %p\n", maxClassName, this, this->maxOb, this->maxObex);
}

template <class T>
MaxCpp<T> :: ~MaxCpp()
{
	maxEnable = 0;
	DEBUG("destructor");
}

template <class T>
void MaxCpp<T>::maxMSPCreate(unsigned int ins, unsigned int outs)
{
	dsp_setup((t_pxobject *)this, ins);	// create DSP inlets
	for (unsigned int i=0;i<outs;i++) outlet_new((t_object *)this, "signal"); // add signal outlets
	maxMSPNumInlets = ins;
	maxMSPNumOutlets = outs;
	
	// prevent recycling of inputs for outputs
	maxOb.z_misc = Z_NO_INPLACE;
	maxMSPEnable = sys_getdspstate();
}

template <class T>
void MaxCpp<T>::maxMSPEnd()
{
	maxMSPEnable = false;
	dsp_free((t_pxobject *)this);
}

template <class T>
void MaxCpp<T>::maxMSPState(T * x, long n)
{
	// gets called when DSP settings have changed
	x->maxMSPEnable = n;
}

template <class T>
void MaxCpp<T>::maxMSPSetup(T * x, t_signal **sp, short *count)
{
	int inlets = x->maxMSPNumInlets;
	int outlets = x->maxMSPNumOutlets;
	
	x->maxMSPInlets.clear();
	x->maxMSPOutlets.clear();
	
	for (int i=0; i<inlets; i++)
		x->maxMSPInlets.push_back(sp[i]->s_vec);
		
	for (int i=inlets; i<inlets+outlets; i++)
		x->maxMSPOutlets.push_back(sp[i]->s_vec);
	
	void ** maxMSPPerform_args = (void **) sysmem_newptr(sizeof(void*) * 4);
	maxMSPPerform_args[0] = (void *) x;
	maxMSPPerform_args[1] = (void *) sp[0]->s_n;	
	maxMSPPerform_args[2] = (void *)(&x->maxMSPInlets);
	maxMSPPerform_args[3] = (void *)(&x->maxMSPOutlets);
		
	dsp_addv(MaxCpp<T>::maxMSPPerform, 4, maxMSPPerform_args);
}

template <class T>
t_class * MaxCpp<T>::maxMSPMakeClass(char * name, maxMSPCallbackType cb)
{
	t_class * c = maxMakeClass(name);
	
	maxMSPUserCallback = cb;
	class_addmethod(c, (method)MaxCpp<T>::maxMSPSetup, "dsp", A_CANT, 0); 
	class_addmethod(c, (method)MaxCpp<T>::maxMSPState,"dspstate", A_CANT, 0);
	class_dspinit(c);
	
	return c;
}

template <class T>
t_int *	MaxCpp<T>::maxMSPPerform(t_int * w)
{
	if (sys_getdspstate()) 
	{
		T * self = (T *)(w[1]);
		unsigned long vs = (unsigned long)(w[2]);	// vector size
		signalArray * inlets = (signalArray *)(w[3]);
		signalArray * outlets = (signalArray *)(w[4]);
		
		// call user-handler here
		if (self->maxEnable && self->maxMSPEnable)
			(((T *)self)->*maxMSPUserCallback)(vs, *inlets, *outlets);
	}

	return w + 5;
}


#endif